/*
 * @Author: EnderByEndera
 * @Date: 2020-12-02 17:08:59
 * @LastEditTime: 2021-02-01 06:54:12
 * @LastEditors: Please set LastEditors
 * @Description: Get commands from file or network
 * @FilePath: /commdetection/preprocessing/commget.go
 */

package comm

import (
	"bytes"
	"commdetection/logger"
	"commdetection/model"
	"commdetection/yaml"
	"io/ioutil"
	"os/exec"
	"sort"
	"strconv"
	"strings"
	"time"
)

// GetCommands returns a list of commands preprocessed which first get commands from file then net
func GetCommands() model.Commands {
	commands, err := getCommandsFromHist()
	if err != nil {
		logger.Warnln(err)
		return []model.Command{}
	}
	sort.Sort(commands)
	return commands
}

// getCommandsFromHist gets the commands by using `history` bash command
func getCommandsFromHist() (model.Commands, error) {
	var (
		stderr bytes.Buffer
		stdout bytes.Buffer
	)
	hist := exec.Command("/bin/bash", "-c", `$COMMDEPATH/history.sh`)
	hist.Stderr = &stderr
	hist.Stdout = &stdout
	err := hist.Run()
	if err != nil {
		return nil, err
	}
	commands := commandsFromString(stdout.String())
	return commands, nil
}

func commandsFromString(s string) model.Commands {
	var commands model.Commands
	commLines := strings.Split(s, "\n")
	for _, comm := range commLines {
		newComm := model.Command{}
		var timestamp string
		for num, commArg := range strings.Fields(comm) {
			if _, err := strconv.Atoi(commArg); err != nil && num == 0 {
				break
			}
			switch {
			case num == 0:
				continue
			case num == 1:
				timestamp = commArg
			case num == 2:
				timestamp += " " + commArg // create the timestamp string to parse
				var err error
				newComm.TimeStamp, err = time.Parse("2006-01-02 15:04:05", timestamp)
				if err != nil {
					newComm.TimeStamp = time.Time{}
				}
			case num == 3:
				newComm.Mac = commArg // comm[3] arg is Mac Address
			case num == 4:
				newComm.User = commArg // comm[4] arg is Username
			case num == 5:
				newComm.CommName = commArg // comm[5] arg is command name
			default:
				if commArg[0] != '-' { // comm is a flag
					newComm.Args = append(newComm.Args, commArg)
				} else { // comm is just a normal argument
					newComm.Flags = append(newComm.Flags, commArg)
				}
			}
		}
		if newComm.CommName != "" {
			commands = append(commands, newComm)
		}
	}
	return commands
}

func getCommandsFromDB() (model.Commands, error) {
	commands := new(model.Commands)
	commands.GetCommandsFrom(yaml.GetMongoSetting().Db, yaml.GetMongoSetting().Collection)
	return *commands, nil
}

func getCommandsFromFile(f string) ([]model.Command, error) {
	var commands []model.Command
	buf, err := ioutil.ReadFile(f)
	if err != nil {
		return nil, err
	}
	lines := strings.Split(string(buf), "\n")
	for _, line := range lines {
		commands = append(commands, splitCommandsInLine(strings.Fields(line))...)
	}
	return commands, nil
}

func splitCommandsInLine(tComm []string) []model.Command {
	commands := []model.Command{}
	newComm := model.Command{}
	for commNum, comm := range tComm {
		if commNum == 0 {
			newComm.CommName = comm
		} else if comm == "&&" { // comm is the separator of the whole command
			commands = append(commands, splitCommandsInLine(tComm[commNum+1:])...)
			break
		} else if comm[0] != '-' { // comm is a flag
			newComm.Args = append(newComm.Args, comm)
		} else { // comm is just a normal argument
			newComm.Flags = append(newComm.Flags, comm)
		}
	}
	if newComm.CommName != "" {
		commands = append([]model.Command{newComm}, commands...)
	}
	return commands
}
